પાયથનના __slots__ નો ઉપયોગ કરી મેમરીનો વપરાશ ઘટાડો અને એટ્રિબ્યુટ એક્સેસની ઝડપ વધારો. બેન્ચમાર્ક, ફાયદા-ગેરફાયદા અને શ્રેષ્ઠ પદ્ધતિઓ સાથેની સંપૂર્ણ માર્ગદર્શિકા.
પાયથનનું __slots__: મેમરી ઓપ્ટિમાઇઝેશન અને એટ્રિબ્યુટ સ્પીડમાં ઊંડાણપૂર્વકનો અભ્યાસ
સોફ્ટવેર ડેવલપમેન્ટની દુનિયામાં, પર્ફોર્મન્સ સર્વોપરી છે. પાયથન ડેવલપર્સ માટે, આનો અર્થ ઘણીવાર ભાષાની અકલ્પનીય લવચીકતા અને સંસાધન કાર્યક્ષમતાની જરૂરિયાત વચ્ચે નાજુક સંતુલન જાળવવાનો હોય છે. સૌથી સામાન્ય પડકારોમાંનો એક, ખાસ કરીને ડેટા-ઇન્ટેન્સિવ એપ્લિકેશન્સમાં, મેમરીના વપરાશનું સંચાલન કરવું છે. જ્યારે તમે લાખો, કે અબજો, નાના ઓબ્જેક્ટ્સ બનાવી રહ્યા હોવ, ત્યારે દરેક બાઇટની ગણતરી થાય છે.
અહીં જ પાયથનનું એક ઓછું જાણીતું પરંતુ શક્તિશાળી ફીચર અમલમાં આવે છે: __slots__. તેને ઘણીવાર મેમરી ઓપ્ટિમાઇઝેશન માટે જાદુઈ ગોળી તરીકે ઓળખવામાં આવે છે, પરંતુ તેનું સાચું સ્વરૂપ વધુ સૂક્ષ્મ છે. શું તે ફક્ત મેમરી બચાવવા માટે છે? શું તે ખરેખર તમારા કોડને ઝડપી બનાવે છે? અને તેનો ઉપયોગ કરવાની છુપી કિંમત શું છે?
આ વ્યાપક માર્ગદર્શિકા તમને પાયથનના __slots__ માં ઊંડાણપૂર્વક લઈ જશે. અમે વિશ્લેષણ કરીશું કે સ્ટાન્ડર્ડ પાયથન ઓબ્જેક્ટ્સ કેવી રીતે કામ કરે છે, મેમરી અને સ્પીડ પર __slots__ ની વાસ્તવિક અસરનું બેન્ચમાર્ક કરીશું, તેની આશ્ચર્યજનક જટિલતાઓ અને ફાયદા-ગેરફાયદાનું અન્વેષણ કરીશું, અને આ શક્તિશાળી ઓપ્ટિમાઇઝેશન ટૂલનો ક્યારે ઉપયોગ કરવો—અને ક્યારે ન કરવો—તે નક્કી કરવા માટે એક સ્પષ્ટ માળખું પ્રદાન કરીશું.
ડિફોલ્ટ: પાયથન ઓબ્જેક્ટ્સ `__dict__` સાથે એટ્રિબ્યુટ્સ કેવી રીતે સંગ્રહિત કરે છે
આપણે __slots__ શું કરે છે તેની પ્રશંસા કરીએ તે પહેલાં, આપણે સમજવું જોઈએ કે તે કોને બદલે છે. ડિફોલ્ટ રૂપે, પાયથનમાં દરેક કસ્ટમ ક્લાસના ઇન્સ્ટન્સમાં __dict__ નામનો એક ખાસ એટ્રિબ્યુટ હોય છે. આ શાબ્દિક રીતે એક ડિક્શનરી છે જે ઇન્સ્ટન્સના તમામ એટ્રિબ્યુટ્સનો સંગ્રહ કરે છે.
ચાલો એક સરળ ઉદાહરણ જોઈએ: 2D પોઇન્ટ રજૂ કરવા માટેનો ક્લાસ.
import sys
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y
# Create an instance
p1 = Point2D(10, 20)
# Attributes are stored in __dict__
print(p1.__dict__) # Output: {'x': 10, 'y': 20}
# Let's check the size of the __dict__ itself
print(f"Size of the Point2D instance's __dict__: {sys.getsizeof(p1.__dict__)} bytes")
આઉટપુટ તમારા પાયથન વર્ઝન અને સિસ્ટમ આર્કિટેક્ચરના આધારે થોડું અલગ હોઈ શકે છે (દા.ત., પાયથન 3.10+ પર નાની ડિક્શનરી માટે 64 બાઇટ્સ), પરંતુ મુખ્ય મુદ્દો એ છે કે આ ડિક્શનરીની પોતાની મેમરી ફૂટપ્રિન્ટ હોય છે, જે ઇન્સ્ટન્સ ઓબ્જેક્ટ અને તેમાં રહેલા મૂલ્યોથી અલગ હોય છે.
લવચીકતાની શક્તિ અને કિંમત
આ __dict__ અભિગમ પાયથનની ગતિશીલતાનો પાયાનો પથ્થર છે. તે તમને કોઈપણ સમયે ઇન્સ્ટન્સમાં નવા એટ્રિબ્યુટ્સ ઉમેરવાની મંજૂરી આપે છે, આ પ્રથાને ઘણીવાર "મંકી-પેચિંગ" કહેવાય છે:
# Add a new attribute on the fly
p1.z = 30
print(p1.__dict__) # Output: {'x': 10, 'y': 20, 'z': 30}
આ લવચીકતા ઝડપી વિકાસ અને અમુક પ્રોગ્રામિંગ પેટર્ન માટે અદ્ભુત છે. જોકે, તેની એક કિંમત છે: મેમરી ઓવરહેડ.
પાયથનમાં ડિક્શનરીઓ અત્યંત ઓપ્ટિમાઇઝ્ડ હોય છે પરંતુ તે સરળ ડેટા સ્ટ્રક્ચર્સ કરતાં સ્વાભાવિક રીતે વધુ જટિલ હોય છે. તેમને ઝડપી કી લુકઅપ પ્રદાન કરવા માટે હેશ ટેબલ જાળવવાની જરૂર પડે છે, જેને સંભવિત હેશ અથડામણોનું સંચાલન કરવા અને કાર્યક્ષમ રિસાઇઝિંગ માટે વધારાની મેમરીની જરૂર પડે છે. જ્યારે તમે લાખો Point2D ઇન્સ્ટન્સ બનાવો છો, અને દરેક પોતાની __dict__ ધરાવે છે, ત્યારે આ મેમરી ઓવરહેડ ઝડપથી જમા થાય છે.
એવી એપ્લિકેશનની કલ્પના કરો કે જે 10 મિલિયન વર્ટિસિસ સાથે 3D મોડેલ પર પ્રક્રિયા કરી રહી છે. જો દરેક વર્ટેક્સ ઓબ્જેક્ટમાં 64 બાઇટ્સની __dict__ હોય, તો તે 640 મેગાબાઇટ્સ મેમરી ફક્ત ડિક્શનરીઓ દ્વારા જ વપરાય છે, તેમાં સંગ્રહિત વાસ્તવિક ઇન્ટિજર કે ફ્લોટ મૂલ્યોની ગણતરી કર્યા વિના! આ તે સમસ્યા છે જેને ઉકેલવા માટે __slots__ ડિઝાઇન કરવામાં આવ્યું હતું.
__slots__ નો પરિચય: મેમરી-બચાવનો વિકલ્પ
__slots__ એ એક ક્લાસ વેરિયેબલ છે જે તમને સ્પષ્ટપણે જાહેર કરવાની મંજૂરી આપે છે કે ઇન્સ્ટન્સમાં કયા એટ્રિબ્યુટ્સ હશે. __slots__ વ્યાખ્યાયિત કરીને, તમે અનિવાર્યપણે પાયથનને કહી રહ્યા છો: "આ ક્લાસના ઇન્સ્ટન્સમાં ફક્ત આ ચોક્કસ એટ્રિબ્યુટ્સ જ હશે. તમારે તેમના માટે __dict__ બનાવવાની જરૂર નથી."
ડિક્શનરીને બદલે, પાયથન ઇન્સ્ટન્સ માટે મેમરીમાં નિશ્ચિત જગ્યા અનામત રાખે છે, જે જાહેર કરાયેલા એટ્રિબ્યુટ્સના મૂલ્યોના પોઇન્ટર્સને સંગ્રહિત કરવા માટે પૂરતી હોય છે, જે સી સ્ટ્રક્ટ (C struct) અથવા ટપલ (tuple) જેવું છે.
ચાલો આપણા Point2D ક્લાસને __slots__ નો ઉપયોગ કરવા માટે રિફેક્ટર કરીએ.
class SlottedPoint2D:
# Declare the instance attributes
# It can be a tuple (most common), list, or any iterable of strings.
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
સપાટી પર, તે લગભગ સમાન દેખાય છે. પરંતુ અંદરથી, બધું બદલાઈ ગયું છે. __dict__ જતું રહ્યું છે.
p_slotted = SlottedPoint2D(10, 20)
# Trying to access __dict__ will raise an error
try:
print(p_slotted.__dict__)
except AttributeError as e:
print(e) # Output: 'SlottedPoint2D' object has no attribute '__dict__'
મેમરી બચતનું બેન્ચમાર્કિંગ
ખરો "વાહ" ક્ષણ ત્યારે આવે છે જ્યારે આપણે મેમરીના વપરાશની તુલના કરીએ છીએ. આ સચોટ રીતે કરવા માટે, આપણે સમજવાની જરૂર છે કે ઓબ્જેક્ટનું કદ કેવી રીતે માપવામાં આવે છે. sys.getsizeof() ઓબ્જેક્ટના મૂળભૂત કદની જાણ કરે છે, પરંતુ તે જે વસ્તુઓનો ઉલ્લેખ કરે છે, જેમ કે __dict__, તેના કદની નહીં.
import sys
# --- Regular Class ---
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y
# --- Slotted Class ---
class SlottedPoint2D:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
# Create one instance of each to compare
p_normal = Point2D(1, 2)
p_slotted = SlottedPoint2D(1, 2)
# The size of the slotted instance is much smaller
# It's typically the base object size plus a pointer for each slot.
size_slotted = sys.getsizeof(p_slotted)
# The size of the normal instance includes its base size and a pointer to its __dict__.
# The total size is the instance size + the __dict__ size.
size_normal = sys.getsizeof(p_normal) + sys.getsizeof(p_normal.__dict__)
print(f"Size of a single SlottedPoint2D instance: {size_slotted} bytes")
print(f"Total memory footprint of a single Point2D instance: {size_normal} bytes")
# Now let's see the impact at scale
NUM_INSTANCES = 1_000_000
# In a real application, you would use a tool like memory_profiler
# to measure the total memory usage of the process.
# We can estimate the savings based on our single-instance calculation.
size_diff_per_instance = size_normal - size_slotted
total_memory_saved = size_diff_per_instance * NUM_INSTANCES
print(f"\nCreating {NUM_INSTANCES:,} instances...")
print(f"Memory saved per instance by using __slots__: {size_diff_per_instance} bytes")
print(f"Estimated total memory saved: {total_memory_saved / (1024*1024):.2f} MB")
એક સામાન્ય 64-બીટ સિસ્ટમ પર, તમે પ્રતિ ઇન્સ્ટન્સ 40-50% મેમરી બચતની અપેક્ષા રાખી શકો છો. એક સામાન્ય ઓબ્જેક્ટ તેના બેઝ માટે 16 બાઇટ્સ + __dict__ પોઇન્ટર માટે 8 બાઇટ્સ + ખાલી __dict__ માટે 64 બાઇટ્સ, કુલ 88 બાઇટ્સ લઈ શકે છે. બે એટ્રિબ્યુટ્સવાળો સ્લોટેડ ઓબ્જેક્ટ ફક્ત 32 બાઇટ્સ લઈ શકે છે. પ્રતિ ઇન્સ્ટન્સ ~56 બાઇટ્સનો આ તફાવત એક મિલિયન ઇન્સ્ટન્સ માટે 56 MB બચતમાં પરિણમે છે. આ માઇક્રો-ઓપ્ટિમાઇઝેશન નથી; તે એક મૂળભૂત ફેરફાર છે જે અશક્ય એપ્લિકેશનને શક્ય બનાવી શકે છે.
બીજું વચન: ઝડપી એટ્રિબ્યુટ એક્સેસ
મેમરી બચત ઉપરાંત, __slots__ ને પર્ફોર્મન્સ સુધારવા માટે પણ વખાણવામાં આવે છે. સિદ્ધાંત સાચો છે: નિશ્ચિત મેમરી ઓફસેટમાંથી મૂલ્ય એક્સેસ કરવું (જેમ કે એરે ઇન્ડેક્સ) ડિક્શનરીમાં હેશ લુકઅપ કરવા કરતાં ઝડપી છે.
__dict__એક્સેસ:obj.xમાં કી'x'માટે ડિક્શનરી લુકઅપનો સમાવેશ થાય છે.__slots__એક્સેસ:obj.xમાં એક વિશિષ્ટ સ્લોટ પર સીધો મેમરી એક્સેસ શામેલ છે.
પણ વ્યવહારમાં તે કેટલું ઝડપી છે? ચાલો તે જાણવા માટે પાયથનના બિલ્ટ-ઇન timeit મોડ્યુલનો ઉપયોગ કરીએ.
import timeit
# Setup code to be run once before timing
SETUP_CODE = """
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y
class SlottedPoint2D:
__slots__ = 'x', 'y'
def __init__(self, x, y):
self.x = x
self.y = y
p_normal = Point2D(1, 2)
p_slotted = SlottedPoint2D(1, 2)
"""
# Test attribute reading
read_normal = timeit.timeit("p_normal.x", setup=SETUP_CODE, number=10_000_000)
read_slotted = timeit.timeit("p_slotted.x", setup=SETUP_CODE, number=10_000_000)
print("--- Attribute Reading ---")
print(f"Time for __dict__ access: {read_normal:.4f} seconds")
print(f"Time for __slots__ access: {read_slotted:.4f} seconds")
speedup = (read_normal - read_slotted) / read_normal * 100
print(f"Speedup: {speedup:.2f}%")
print("\n--- Attribute Writing ---")
# Test attribute writing
write_normal = timeit.timeit("p_normal.x = 3", setup=SETUP_CODE, number=10_000_000)
write_slotted = timeit.timeit("p_slotted.x = 3", setup=SETUP_CODE, number=10_000_000)
print(f"Time for __dict__ access: {write_normal:.4f} seconds")
print(f"Time for __slots__ access: {write_slotted:.4f} seconds")
speedup = (write_normal - write_slotted) / write_normal * 100
print(f"Speedup: {speedup:.2f}%")
પરિણામો બતાવશે કે __slots__ ખરેખર ઝડપી છે, પરંતુ સુધારો સામાન્ય રીતે 10-20% ની રેન્જમાં હોય છે. જોકે તે નજીવું નથી, તે મેમરી બચત કરતાં ઘણું ઓછું નાટકીય છે.
મુખ્ય મુદ્દો: __slots__ નો ઉપયોગ મુખ્યત્વે મેમરી ઓપ્ટિમાઇઝેશન માટે કરો. ગતિમાં સુધારાને એક સ્વાગત, પરંતુ ગૌણ, બોનસ તરીકે ગણો. પર્ફોર્મન્સમાં લાભ એવા ગણતરીની દ્રષ્ટિએ સઘન અલ્ગોરિધમ્સની અંદર ટાઇટ લૂપ્સમાં સૌથી વધુ સુસંગત છે જ્યાં એટ્રિબ્યુટ એક્સેસ લાખો વખત થાય છે.
ફાયદા-ગેરફાયદા અને "મુશ્કેલીઓ": `__slots__` સાથે તમે શું ગુમાવો છો
__slots__ મફત નથી. પર્ફોર્મન્સના લાભો લવચીકતાના ભોગે આવે છે અને કેટલીક જટિલતાઓ રજૂ કરે છે, ખાસ કરીને ઇનહેરિટન્સના સંબંધમાં. આ ફાયદા-ગેરફાયદાને સમજવું __slots__ નો અસરકારક રીતે ઉપયોગ કરવા માટે નિર્ણાયક છે.
1. ડાયનેમિક એટ્રિબ્યુટ્સની ખોટ
આ સૌથી નોંધપાત્ર પરિણામ છે. એટ્રિબ્યુટ્સને પૂર્વ-વ્યાખ્યાયિત કરીને, તમે રનટાઇમ પર નવા ઉમેરવાની ક્ષમતા ગુમાવો છો.
p_slotted = SlottedPoint2D(10, 20)
# This works fine
p_slotted.x = 100
# This will fail
try:
p_slotted.z = 30 # 'z' was not in __slots__
except AttributeError as e:
print(e) # Output: 'SlottedPoint2D' object has no attribute 'z'
આ વર્તન ભૂલને બદલે એક ફીચર હોઈ શકે છે. તે વધુ કડક ઓબ્જેક્ટ મોડેલ લાગુ કરે છે, આકસ્મિક એટ્રિબ્યુટ બનાવટને અટકાવે છે અને ક્લાસના "આકાર" ને વધુ અનુમાનિત બનાવે છે. જોકે, જો તમારી ડિઝાઇન ડાયનેમિક એટ્રિબ્યુટ અસાઇનમેન્ટ પર આધાર રાખે છે, તો __slots__ કોઈ વિકલ્પ નથી.
2. `__dict__` અને `__weakref__` ની ગેરહાજરી
જેમ આપણે જોયું છે, __slots__ __dict__ ની રચનાને અટકાવે છે. જો તમારે __dict__ દ્વારા ઇન્ટ્રોસ્પેક્શન પર આધાર રાખતી લાઇબ્રેરીઓ અથવા ટૂલ્સ સાથે કામ કરવાની જરૂર હોય તો આ સમસ્યારૂપ બની શકે છે.
તેવી જ રીતે, __slots__ __weakref__ ની સ્વચાલિત રચનાને પણ અટકાવે છે, જે એક એટ્રિબ્યુટ છે જે ઓબ્જેક્ટને વીકલી રેફરન્સ કરી શકાય તે માટે જરૂરી છે. વીક રેફરન્સ એ એક એડવાન્સ્ડ મેમરી મેનેજમેન્ટ ટૂલ છે જેનો ઉપયોગ ઓબ્જેક્ટ્સને ગાર્બેજ કલેક્ટ થતા અટકાવ્યા વિના તેમને ટ્રેક કરવા માટે થાય છે.
ઉકેલ: જો તમને જરૂર હોય તો તમે સ્પષ્ટપણે '__dict__' અને '__weakref__' ને તમારી __slots__ વ્યાખ્યામાં શામેલ કરી શકો છો.
class HybridSlottedPoint:
# We get memory savings for x and y, but still have __dict__ and __weakref__
__slots__ = ('x', 'y', '__dict__', '__weakref__')
def __init__(self, x, y):
self.x = x
self.y = y
p_hybrid = HybridSlottedPoint(5, 10)
p_hybrid.z = 20 # This works now, because __dict__ is present!
print(p_hybrid.__dict__) # Output: {'z': 20}
import weakref
w_ref = weakref.ref(p_hybrid) # This also works now
print(w_ref)
'__dict__' ઉમેરવાથી તમને એક હાઇબ્રિડ મોડેલ મળે છે. સ્લોટેડ એટ્રિબ્યુટ્સ (x, y) હજુ પણ કાર્યક્ષમ રીતે હેન્ડલ થાય છે, જ્યારે અન્ય કોઈપણ એટ્રિબ્યુટ્સ __dict__ માં મૂકવામાં આવે છે. આનાથી મેમરી બચતના કેટલાક લાભો નકારી શકાય છે પરંતુ સૌથી સામાન્ય એટ્રિબ્યુટ્સને ઓપ્ટિમાઇઝ કરતી વખતે લવચીકતા જાળવી રાખવા માટે તે એક ઉપયોગી સમાધાન હોઈ શકે છે.
3. ઇનહેરિટન્સની જટિલતાઓ
આ તે સ્થાન છે જ્યાં __slots__ મુશ્કેલ બની શકે છે. તેની વર્તણૂક પેરેન્ટ અને ચાઇલ્ડ ક્લાસ કેવી રીતે વ્યાખ્યાયિત કરવામાં આવે છે તેના પર આધાર રાખે છે.
સિંગલ ઇનહેરિટન્સ
-
જો પેરેન્ટ ક્લાસમાં
__slots__હોય પરંતુ ચાઇલ્ડમાં ન હોય: ચાઇલ્ડ ક્લાસ પેરેન્ટના એટ્રિબ્યુટ્સ માટે સ્લોટેડ વર્તન વારસામાં મેળવશે પરંતુ તેની પોતાની__dict__પણ હશે. આનો અર્થ એ છે કે ચાઇલ્ડ ક્લાસના ઇન્સ્ટન્સ પેરેન્ટના ઇન્સ્ટન્સ કરતાં મોટા હશે.class SlottedBase: __slots__ = ('a',) class DictChild(SlottedBase): # No __slots__ defined here def __init__(self): self.a = 1 self.b = 2 # 'b' will be stored in __dict__ c = DictChild() print(f"Child has __dict__: {hasattr(c, '__dict__')}") # Output: True print(c.__dict__) # Output: {'b': 2} -
જો પેરેન્ટ અને ચાઇલ્ડ બંને ક્લાસ
__slots__વ્યાખ્યાયિત કરે છે: ચાઇલ્ડ ક્લાસમાં__dict__નહીં હોય. તેના અસરકારક__slots__તેના પોતાના__slots__અને તેના પેરેન્ટના__slots__નું સંયોજન હશે.મહત્વપૂર્ણ: જો પેરેન્ટનાclass SlottedBase: __slots__ = ('a',) class SlottedChild(SlottedBase): __slots__ = ('b',) # Effective slots are ('a', 'b') def __init__(self): self.a = 1 self.b = 2 sc = SlottedChild() print(f"Child has __dict__: {hasattr(sc, '__dict__')}") # Output: False try: sc.c = 3 # Raises AttributeError except AttributeError as e: print(e)__slots__માં એવો એટ્રિબ્યુટ હોય જે ચાઇલ્ડના__slots__માં પણ સૂચિબદ્ધ હોય, તો તે રીડન્ડન્ટ છે પરંતુ સામાન્ય રીતે હાનિકારક નથી.
મલ્ટીપલ ઇનહેરિટન્સ
__slots__ સાથે મલ્ટીપલ ઇનહેરિટન્સ એ એક માઇનફિલ્ડ છે. નિયમો કડક છે અને અનપેક્ષિત ભૂલો તરફ દોરી શકે છે.
-
મુખ્ય નિયમ: ચાઇલ્ડ ક્લાસ માટે
__slots__નો અસરકારક રીતે ઉપયોગ કરવા (એટલે કે,__dict__વિના), બધા પેરેન્ટ ક્લાસમાં પણ__slots__હોવું આવશ્યક છે. જો એક પણ પેરેન્ટ ક્લાસમાં__slots__ન હોય (અને આમ__dict__હોય), તો ચાઇલ્ડ ક્લાસમાં પણ__dict__હશે. -
TypeErrorટ્રેપ: ચાઇલ્ડ ક્લાસ એવા બહુવિધ પેરેન્ટ ક્લાસમાંથી વારસો મેળવી શકતો નથી કે જે બંનેમાં ખાલી ન હોય તેવા__slots__હોય.આ પ્રતિબંધ અસ્તિત્વમાં છે કારણ કે સ્લોટેડ ઓબ્જેક્ટ્સ માટે મેમરી લેઆઉટ ક્લાસ બનાવતી વખતે નિશ્ચિત કરવામાં આવે છે. પાયથન એક સુસંગત અને અસ્પષ્ટ મેમરી લેઆઉટ બનાવી શકતું નથી જે બે સ્વતંત્ર પેરેન્ટ ક્લાસના સ્લોટ્સને જોડે.class SlotParentA: __slots__ = ('x',) class SlotParentB: __slots__ = ('y',) try: class ProblemChild(SlotParentA, SlotParentB): pass except TypeError as e: print(e) # Output: multiple bases have instance lay-out conflict
નિર્ણય: `__slots__` નો ક્યારે ઉપયોગ કરવો અને ક્યારે નહીં
ફાયદા અને ગેરફાયદાની સ્પષ્ટ સમજ સાથે, અમે વ્યવહારુ નિર્ણય લેવાનું માળખું સ્થાપિત કરી શકીએ છીએ.
ગ્રીન ફ્લેગ્સ: `__slots__` નો ઉપયોગ કરો જ્યારે...
- તમે મોટી સંખ્યામાં ઇન્સ્ટન્સ બનાવી રહ્યા છો. આ પ્રાથમિક ઉપયોગનો કેસ છે. જો તમે લાખો ઓબ્જેક્ટ્સ સાથે કામ કરી રહ્યા છો, તો મેમરી બચત એક એવી એપ્લિકેશન વચ્ચેનો તફાવત હોઈ શકે છે જે ચાલે છે અને જે ક્રેશ થાય છે.
-
ઓબ્જેક્ટના એટ્રિબ્યુટ્સ નિશ્ચિત અને અગાઉથી જાણીતા હોય છે.
__slots__ડેટા સ્ટ્રક્ચર્સ, રેકોર્ડ્સ અથવા સાદા ડેટા ઓબ્જેક્ટ્સ માટે યોગ્ય છે જેનો "આકાર" બદલાતો નથી. - તમે મેમરી-પ્રતિબંધિત વાતાવરણમાં છો. આમાં IoT ઉપકરણો, મોબાઇલ એપ્લિકેશન્સ અથવા ઉચ્ચ-ઘનતાવાળા સર્વર્સનો સમાવેશ થાય છે જ્યાં દરેક મેગાબાઇટ કિંમતી છે.
-
તમે પર્ફોર્મન્સ બોટલનેકને ઓપ્ટિમાઇઝ કરી રહ્યા છો. જો પ્રોફાઇલિંગ બતાવે છે કે ટાઇટ લૂપમાં એટ્રિબ્યુટ એક્સેસ નોંધપાત્ર ધીમું છે, તો
__slots__માંથી મળતો સાધારણ સ્પીડ બૂસ્ટ યોગ્ય હોઈ શકે છે.
સામાન્ય ઉદાહરણો:
- એક મોટા ગ્રાફ અથવા ટ્રી સ્ટ્રક્ચરમાં નોડ્સ.
- ફિઝિક્સ સિમ્યુલેશનમાં કણો.
- એક મોટી ડેટાબેઝ ક્વેરીમાંથી પંક્તિઓ રજૂ કરતા ઓબ્જેક્ટ્સ.
- ઉચ્ચ-થ્રુપુટ સિસ્ટમમાં ઇવેન્ટ અથવા સંદેશ ઓબ્જેક્ટ્સ.
રેડ ફ્લેગ્સ: `__slots__` ટાળો જ્યારે...
-
લવચીકતા મુખ્ય છે. જો તમારો ક્લાસ સામાન્ય હેતુના ઉપયોગ માટે ડિઝાઇન કરવામાં આવ્યો હોય અથવા જો તમે ડાયનેમિકલી એટ્રિબ્યુટ્સ ઉમેરવા પર આધાર રાખતા હોવ (મંકી-પેચિંગ), તો ડિફોલ્ટ
__dict__સાથે રહો. -
તમારો ક્લાસ જાહેર API નો ભાગ છે જે અન્ય લોકો દ્વારા સબક્લાસિંગ માટે બનાવાયેલ છે. બેઝ ક્લાસ પર
__slots__લાદવાથી બધા ચાઇલ્ડ ક્લાસ પર નિયંત્રણો લાદવામાં આવે છે, જે તમારા વપરાશકર્તાઓ માટે અનિચ્છનીય આશ્ચર્ય બની શકે છે. -
તમે એટલા બધા ઇન્સ્ટન્સ નથી બનાવી રહ્યા કે ફરક પડે. જો તમારી પાસે ફક્ત થોડાક સો કે હજાર ઇન્સ્ટન્સ હોય, તો મેમરી બચત નજીવી હશે. અહીં
__slots__લાગુ કરવું એ અકાળ ઓપ્ટિમાઇઝેશન છે જે કોઈ વાસ્તવિક લાભ વિના જટિલતા ઉમેરે છે. -
તમે જટિલ મલ્ટીપલ ઇનહેરિટન્સ હાયરાર્કી સાથે કામ કરી રહ્યા છો.
TypeErrorપ્રતિબંધો આ દૃશ્યોમાં__slots__ને તેની કિંમત કરતાં વધુ મુશ્કેલીભર્યું બનાવી શકે છે.
આધુનિક વિકલ્પો: શું `__slots__` હજી પણ શ્રેષ્ઠ પસંદગી છે?
પાયથનનું ઇકોસિસ્ટમ વિકસિત થયું છે, અને __slots__ હવે હલકા વજનના ઓબ્જેક્ટ્સ બનાવવા માટેનું એકમાત્ર સાધન નથી. આધુનિક પાયથન કોડ માટે, તમારે આ ઉત્તમ વિકલ્પો પર વિચાર કરવો જોઈએ.
collections.namedtuple અને typing.NamedTuple
નેમ્ડટપલ્સ (Namedtuples) એ નામવાળા ફીલ્ડ્સ સાથે ટપલ સબક્લાસ બનાવવા માટેની ફેક્ટરી ફંક્શન છે. તે અત્યંત મેમરી-કાર્યક્ષમ છે (સ્લોટેડ ઓબ્જેક્ટ્સ કરતાં પણ વધુ કારણ કે તે અંદરથી ટપલ્સ છે) અને, નિર્ણાયક રીતે, ઇમ્યુટેબલ (immutable) છે.
from typing import NamedTuple
# Creates an immutable class with type hints
class Point(NamedTuple):
x: int
y: int
p = Point(10, 20)
print(p.x) # 10
try:
p.x = 30 # Raises AttributeError: can't set attribute
except AttributeError as e:
print(e)
જો તમને ઇમ્યુટેબલ ડેટા કન્ટેનરની જરૂર હોય, તો NamedTuple એ સ્લોટેડ ક્લાસ કરતાં ઘણીવાર વધુ સારી અને સરળ પસંદગી છે.
બંને વિશ્વમાં શ્રેષ્ઠ: @dataclass(slots=True)
પાયથન 3.7 માં રજૂ કરાયેલ અને પાયથન 3.10 માં ઉન્નત, ડેટાક્લાસીસ એક ગેમ-ચેન્જર છે. તે આપમેળે __init__, __repr__, અને __eq__ જેવી પદ્ધતિઓ જનરેટ કરે છે, જે બોઇલરપ્લેટ કોડને ભારે ઘટાડે છે.
નિર્ણાયક રીતે, @dataclass ડેકોરેટરમાં slots આર્ગ્યુમેન્ટ છે (પાયથન 3.10 થી ઉપલબ્ધ; પાયથન 3.8-3.9 માટે સમાન સુવિધા માટે તૃતીય-પક્ષ લાઇબ્રેરીની જરૂર છે). જ્યારે તમે slots=True સેટ કરો છો, ત્યારે ડેટાક્લાસ વ્યાખ્યાયિત ફીલ્ડ્સના આધારે આપમેળે __slots__ એટ્રિબ્યુટ જનરેટ કરશે.
from dataclasses import dataclass
@dataclass(slots=True)
class DataPoint:
x: int
y: int
dp = DataPoint(10, 20)
print(dp) # Output: DataPoint(x=10, y=20) - nice repr for free!
print(hasattr(dp, '__dict__')) # Output: False - slots are enabled!
આ અભિગમ તમને બધા વિશ્વમાં શ્રેષ્ઠ આપે છે:
- વાંચનક્ષમતા અને સંક્ષિપ્તતા: મેન્યુઅલ ક્લાસ વ્યાખ્યા કરતાં ઘણું ઓછું બોઇલરપ્લેટ.
- સગવડ: સ્વતઃ-જનરેટેડ વિશેષ પદ્ધતિઓ તમને સામાન્ય બોઇલરપ્લેટ લખવાથી બચાવે છે.
- પર્ફોર્મન્સ:
__slots__ના સંપૂર્ણ મેમરી અને ગતિ લાભો. - ટાઇપ સેફ્ટી: પાયથનના ટાઇપિંગ ઇકોસિસ્ટમ સાથે સંપૂર્ણ રીતે સંકલિત થાય છે.
પાયથન 3.10+ માં લખાયેલા નવા કોડ માટે, @dataclass(slots=True) એ સરળ, મ્યુટેબલ, મેમરી-કાર્યક્ષમ ડેટા-હોલ્ડિંગ ક્લાસ બનાવવા માટે તમારી ડિફોલ્ટ પસંદગી હોવી જોઈએ.
નિષ્કર્ષ: એક વિશિષ્ટ કાર્ય માટે એક શક્તિશાળી સાધન
__slots__ એ પાયથનની ડિઝાઇન ફિલોસોફીનું પ્રમાણ છે જે પર્ફોર્મન્સની સીમાઓને આગળ વધારવાની જરૂરિયાતવાળા ડેવલપર્સ માટે શક્તિશાળી સાધનો પૂરા પાડે છે. તે અંધાધૂંધ ઉપયોગમાં લેવાતું લક્ષણ નથી, પરંતુ એક ચોક્કસ અને સામાન્ય સમસ્યાને હલ કરવા માટેનું એક તીક્ષ્ણ, સચોટ સાધન છે: અસંખ્ય નાના ઓબ્જેક્ટ્સની ઉચ્ચ મેમરી કિંમત.
ચાલો __slots__ વિશેના આવશ્યક સત્યોનો સારાંશ આપીએ:
- તેનો પ્રાથમિક લાભ મેમરીના વપરાશમાં નોંધપાત્ર ઘટાડો છે, જે ઘણીવાર ઇન્સ્ટન્સના કદને 40-50% ઘટાડે છે. આ તેનું કિલર ફીચર છે.
- તે એટ્રિબ્યુટ એક્સેસ માટે ગૌણ, વધુ સાધારણ, ગતિમાં વધારો પ્રદાન કરે છે, સામાન્ય રીતે લગભગ 10-20%.
- મુખ્ય ગેરલાભ ડાયનેમિક એટ્રિબ્યુટ અસાઇનમેન્ટની ખોટ છે, જે કડક ઓબ્જેક્ટ માળખું લાગુ કરે છે.
- તે ઇનહેરિટન્સ સાથે જટિલતા રજૂ કરે છે, જેને સાવચેતીપૂર્વક ડિઝાઇન કરવાની જરૂર છે, ખાસ કરીને મલ્ટીપલ ઇનહેરિટન્સ દૃશ્યોમાં.
-
આધુનિક પાયથનમાં,
@dataclass(slots=True)ઘણીવાર એક શ્રેષ્ઠ, વધુ અનુકૂળ વિકલ્પ છે, જે__slots__ના લાભોને ડેટાક્લાસીસની સુંદરતા સાથે જોડે છે.
ઓપ્ટિમાઇઝેશનનો સુવર્ણ નિયમ અહીં લાગુ પડે છે: પહેલા પ્રોફાઇલ કરો. જાદુઈ સ્પીડઅપની આશામાં તમારા કોડબેઝમાં __slots__ નો છંટકાવ ન કરો. કયા ઓબ્જેક્ટ્સ સૌથી વધુ મેમરીનો વપરાશ કરી રહ્યા છે તે ઓળખવા માટે મેમરી પ્રોફાઇલિંગ ટૂલ્સનો ઉપયોગ કરો. જો તમને એવો ક્લાસ મળે જે લાખો વખત ઇન્સ્ટન્ટિએટ થઈ રહ્યો હોય અને મુખ્ય મેમરી હોગ હોય, તો—અને માત્ર ત્યારે જ—__slots__ નો ઉપયોગ કરવાનો સમય છે. તેની શક્તિ અને તેના જોખમોને સમજીને, તમે વૈશ્વિક પ્રેક્ષકો માટે વધુ કાર્યક્ષમ અને સ્કેલેબલ પાયથન એપ્લિકેશન્સ બનાવવા માટે તેનો અસરકારક રીતે ઉપયોગ કરી શકો છો.